using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace EPN.Common
{
    public abstract class WorkerThreadBase : IDisposable
    {
        private Thread _workerThread;
        private ManualResetEvent _stopping;
        private ManualResetEvent _stopped;
        private bool _disposed;
        private bool _disposing;

        protected WorkerThreadBase(): this(null, ThreadPriority.Normal)
        {
        }

        protected WorkerThreadBase(string name)  : this(name, ThreadPriority.Normal)
        {
        }

        protected WorkerThreadBase(string name, ThreadPriority priority) : this(name, priority, false)
        {
        }

        protected WorkerThreadBase(string name, ThreadPriority priority,bool isBackground)
        {
            _disposing = false;
            _disposed = false;
            _stopping = new ManualResetEvent(false);
            _stopped = new ManualResetEvent(false);

            _workerThread = new Thread(threadProc);
            _workerThread.Name = name == null ? GetType().Name : name;
            _workerThread.Priority = priority;
            _workerThread.IsBackground = isBackground;
        }

        protected bool StopRequested
        {
            get { return _stopping.WaitOne(1, true); }
        }

        public void Start()
        {
            ThrowIfDisposedOrDisposing();
            _workerThread.Start();
        }

        public void Stop()
        {
            ThrowIfDisposedOrDisposing();
            _stopping.Set();
            _stopped.WaitOne();
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
        }

        #endregion

        public static void WaitAll(params WorkerThreadBase[] threads)
        { 
            WaitHandle.WaitAll(
                Array.ConvertAll<WorkerThreadBase, WaitHandle>(
                    threads,
                    delegate(WorkerThreadBase workerThread)
                    { return workerThread._stopped; }));
        }

        public static void WaitAny(params WorkerThreadBase[] threads)
        {
            WaitHandle.WaitAny(Array.ConvertAll<WorkerThreadBase, WaitHandle>(
                    threads,delegate(WorkerThreadBase workerThread)
                    { return workerThread._stopped; }));
        }

        protected virtual void Dispose(bool disposing)
        {
            //do nothing if disposed more than once
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                _disposing = disposing;

                //stop the thread;
                Stop();

                //make sure the thread joins the main thread
                _workerThread.Join(1000);

                //dispose of the waithandles
                disposeWaitHandle(_stopping);
                disposeWaitHandle(_stopped);

                _disposing = false;
                //mark as disposed
                _disposed = true;
            }
        }

        protected void ThrowIfDisposedOrDisposing()
        {
            if (_disposing)
            {
                throw new InvalidOperationException("ERROR_OBJECT_DISPOSING");
            }

            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name,"ERROR_OBJECT_DISPOSED");
            }
        }

        protected abstract void Work();

        private void threadProc()
        {
            Work();
            _stopped.Set();
        }

        private void disposeWaitHandle(WaitHandle waitHandle)
        {
            if (waitHandle != null)
            {
                waitHandle.Close();
                waitHandle = null;
            }
        }
    }
}